﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;

namespace Ini4Net
{
    #region Ini Syntax

    /// <summary>
    /// Defines the syntax for our Ini file
    /// </summary>
    public class IniSyntax
    {
        /// <summary>
        /// What characters are comment characters?
        /// default: #, ;
        /// </summary>
        public char[] CommentTokens { get; set; }

        /// <summary>
        /// What character separates the key from the value?
        /// default: =
        /// </summary>
        public char ValueSeparatorToken { get; set; }

        /// <summary>
        /// What characters defines the section header start token?
        /// default: [
        /// </summary>
        public char SectionHeaderStartToken { get; set; }

        /// <summary>
        /// What characters defines the section header end token?
        /// default: ]
        /// </summary>
        public char SectionHeaderEndToken { get; set; }

        /// <summary>
        /// Initialise with our default values
        /// </summary>
        public IniSyntax()
        {
            CommentTokens = new char[] { '#', ';' };
            ValueSeparatorToken = '=';
            SectionHeaderEndToken = ']';
            SectionHeaderStartToken = '[';
        }

    }

    #endregion

    #region IniKey 

    public class IniKey
    {
        public string Key { get; set; }
        public string Value { get; set; }

        public IniKey(string key, string value)
        {
            Key = key;
            Value = value;
        }

        
    }

    #endregion 

    #region IniSection

    public class IniSection : IEnumerable<IniKey>
    {

        #region Private

        private readonly Dictionary<string, IniKey> _keys = new Dictionary<string, IniKey>();
        
        private bool CheckKey(string key)
        {
            if(string.IsNullOrEmpty(key))
            {
                throw new IniKeyNotFoundException(string.Format("The key '{0}' not found in the section", key));
            }
            return _keys.ContainsKey(key);
        }

        #endregion 

        #region Properties

        public string SectionName { get; set; }

        public IniKey this[string key]
        {
            get { return GetKey(key); }
        }

        #endregion 

        #region Add Key

        public void AddKey(string key, string value)
        {
            if (!CheckKey(key.Trim()))
            {
                _keys.Add(key, new IniKey(key.Trim(), value.Trim()));
            } else
            {
                _keys[key].Value = value.Trim();
            }
        }

        #endregion

        #region Get Key

        public IniKey GetKey(string key)
        {
            if (CheckKey(key.Trim()))
            {
                return _keys[key];
            }
            throw new IniKeyNotFoundException(string.Format("The key '{0}' not found in the section", key));
        }

        public T Get<T>(string key)
        {
            CheckKey(key);
            
            if(_keys.ContainsKey(key))
            {
                return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromString(_keys[key].Value);
            }
            throw new IniKeyNotFoundException(string.Format("The key '{0}' not found in the section", key));
        }

        #endregion 

        #region Set Key

        public void Set<T>(string key, T value)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException();
            }
            if (_keys.ContainsKey(key))
            {
                _keys[key].Value = value.ToString();
            }
            else
            {
                throw new IniKeyNotFoundException(string.Format("The key '{0}' not found in the section", key));
            }
        }

        #endregion

        #region IEnumerable

        public IEnumerator<IniKey> GetEnumerator()
        {
            List<IniKey> k = new List<IniKey>();
            foreach (KeyValuePair<string, IniKey> kvp in _keys)
            {
                k.Add(kvp.Value);
            }
            return k.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion 
    }

    #endregion 

    #region Ini

    public class Ini : IEnumerable<IniSection>
    {

        #region Private

        private Dictionary<string, IniSection> _sections = new Dictionary<string, IniSection>();
        public IniSyntax Syntax = new IniSyntax();

        #endregion

        #region Properties

        public IniSection this[string section]
        {
            get { return GetSection(section); }
        }

        #endregion

        #region Get Section

        public IniSection GetSection(string section)
        {
            if(_sections.ContainsKey(section))
            {
                return _sections[section];
            }
            throw new IniSectionNotFoundException(string.Format("The section '{0}' not found in the section", section));
        }

        #endregion 

        #region Add Section

        public void AddSection(string section)
        {
            if(!_sections.ContainsKey(section))
            {
                _sections.Add(section, new IniSection());
            }
        }

        #endregion 

        #region Read

        public bool Read(string fileName)
        {
            if(string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentNullException("fileName");
            }
            if (File.Exists(fileName))
            {
                return Read(File.ReadAllLines(fileName));
            }
            throw new FileNotFoundException();
        }

        public bool Read(string[] contents)
        {
            try
            {
                _sections.Clear();
                string l;
                IniSection s = null;
                for (int i = 0; i < contents.Length; i++)
                {
                    l = contents[i].Trim();

                    if (l.Length > 0)
                    {
                        if (!IsComment(l[0]))
                        {
                            if (IsHeader(l))
                            {
                                if (s != null)
                                {
                                    _sections.Add(s.SectionName, s);
                                }
                                s = new IniSection();

                                s.SectionName = l.Contains(Syntax.SectionHeaderEndToken.ToString())
                                             ? l.Substring(1, l.Length - 2)
                                             : l.Substring(1, l.Length - 1).Trim();
                                continue;
                            }

                            if (s != null)
                            {
                                if (l.Contains(Syntax.ValueSeparatorToken.ToString()))
                                {
                                    int pos = l.IndexOf(Syntax.ValueSeparatorToken);
                                    s.AddKey(l.Substring(0, pos).Trim(), l.Substring(pos + 1).Trim());
                                }
                                else
                                {
                                    s.AddKey(l.Trim(), l.Trim());
                                }
                            }
                        }
                    }
                }
                if (s != null)
                {
                    _sections.Add(s.SectionName, s);
                }
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        #endregion 

        #region Save

        public void Save(string fileName, bool overwrite = true)
        {
            if (File.Exists(fileName) && !overwrite)
            {
                throw new FileLoadException("File Already Exists");
            }

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("#");
            sb.AppendLine("# AutoGenerated on " + DateTime.Now.ToShortDateString() + " " +
                          DateTime.Now.ToShortTimeString());
            sb.AppendLine("#");
            sb.Append(ToString());
            File.WriteAllText(fileName, sb.ToString());
        }

        #endregion 

        #region Is Header

        private bool IsHeader(string s)
        {
            return s.Contains(Syntax.SectionHeaderEndToken.ToString()) &&
                   s.Contains(Syntax.SectionHeaderEndToken.ToString());
        }

        #endregion

        #region Is Comment

        private bool IsComment(char ch)
        {
            foreach (char c in Syntax.CommentTokens)
            {
                if (ch == c)
                {
                    return true;
                }
            }
            return false;
        }

        #endregion

        #region IEnumerable

        public IEnumerator<IniSection> GetEnumerator()
        {
            List<IniSection> l = new List<IniSection>();
            foreach(KeyValuePair<string,IniSection> kvp in _sections)
            {
                l.Add(kvp.Value);
            }
            return l.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion 

        #region ToString()

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (IniSection s in this)
            {
                sb.AppendLine("[" + s.SectionName + "]");
                foreach (IniKey k in s)
                {
                    sb.AppendLine(string.Format("{0} {1} {2}", k.Key, Syntax.ValueSeparatorToken, k.Value));
                }
                sb.AppendLine();
            }
            sb.AppendLine("");
            return sb.ToString();
        }

        #endregion 

    }

    #endregion 

    #region Exceptions

    public class IniSectionNotFoundException : Exception
    {
        public IniSectionNotFoundException(string msg)
            : base(msg)
        {

        }
    }

    public class IniKeyNotFoundException : Exception
    {
        public IniKeyNotFoundException(string msg)
            : base(msg)
        {

        }
    }

    #endregion
}
